package de.slothsoft.network;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Client extends Thread implements ServerClient {

	public static Client createClient(InetAddress inetAddress, int port) throws IOException {
		Socket socket = new Socket(inetAddress, port);
		return new Client(socket);
	}

	private final Socket connection;
	private ObjectInputStream input;
	private ObjectOutputStream output;
	private boolean connected = true;

	private final List<ClientListener> clientListener = new ArrayList<>();

	public Client(Socket socket) throws NetworkException {
		this.connection = socket;
		try {
			this.output = new ObjectOutputStream(this.connection.getOutputStream());
			this.output.flush();
			this.input = new ObjectInputStream(this.connection.getInputStream());
		} catch (IOException e) {
			throw new NetworkException(e);
		}
	}

	@Override
	public void run() {
		while (this.connected) {
			try {
				readIncomingObjects();
			} catch (Exception exception) {
				try {
					System.err.println("ERROR: Disconnecting client - " + exception.getMessage()); //$NON-NLS-1$
					disconnect(exception);
				} catch (NetworkException networkException) {
					System.err.println("ERROR: Client.disconnect() - " + networkException.getMessage()); //$NON-NLS-1$
				}
			}
		}
	}

	protected void readIncomingObjects() throws ClassNotFoundException, IOException {
		Object object = this.input.readObject();
		fireObjectReceived(object);
	}

	public void send(Serializable object) throws IOException {
		synchronized (this.output) {
			this.output.writeObject(object);
			this.output.flush();
		}
	}

	@Override
	public void addClientListener(ClientListener listener) {
		this.clientListener.add(listener);
	}

	@Override
	public void removeClientListener(ClientListener listener) {
		this.clientListener.remove(listener);
	}

	@Override
	public void disconnect() throws NetworkException {
		disconnect(null);
	}

	void disconnect(Exception e) throws NetworkException {
		try {
			this.connected = false;
			this.connection.close();
			fireDisconnected(e);
		} catch (IOException exception) {
			throw new NetworkException(exception);
		}
	}

	protected void fireDisconnected(Exception exception) {
		fireDisconnected(new ClientEvent(this, exception));
	}

	protected void fireDisconnected(ClientEvent event) {
		ClientListener[] array = this.clientListener.toArray(new ClientListener[0]);
		for (ClientListener l : array) {
			l.disconnected(event);
		}
	}

	protected void fireObjectReceived(Object object) {
		fireObjectReceived(new ClientEvent(this, object));
	}

	protected void fireObjectReceived(ClientEvent event) {
		ClientListener[] array = this.clientListener.toArray(new ClientListener[0]);
		for (ClientListener l : array) {
			l.receivedObject(event);
		}
	}

	public ObjectOutputStream getOutputStream() {
		return this.output;
	}

	public ObjectInputStream getInputStream() {
		return this.input;
	}

	public boolean isConnected() {
		return this.connected;
	}

}
